CM3D2 Converter.anm_import
1import re 2import struct 3import math 4import unicodedata 5import time 6import bpy 7import bmesh 8import mathutils 9import os 10from . import common 11from . import compat 12from .translations.pgettext_functions import * 13 14 15# メインオペレーター 16@compat.BlRegister() 17class CNV_OT_import_cm3d2_anm(bpy.types.Operator): 18 bl_idname = 'import_anim.import_cm3d2_anm' 19 bl_label = "CM3D2モーション (.anm)" 20 bl_description = "カスタムメイド3D2のanmファイルを読み込みます" 21 bl_options = {'REGISTER'} 22 23 filepath = bpy.props.StringProperty(subtype='FILE_PATH') 24 filename_ext = ".anm" 25 filter_glob = bpy.props.StringProperty(default="*.anm", options={'HIDDEN'}) 26 27 scale = bpy.props.FloatProperty(name="倍率", default=5, min=0.1, max=100, soft_min=0.1, soft_max=100, step=100, precision=1, description="インポート時のメッシュ等の拡大率です") 28 set_frame_rate = bpy.props.BoolProperty(name="Set Framerate", default=True, description="Change the scene's render settings to 60 fps") 29 is_loop = bpy.props.BoolProperty(name="Loop", default=True) 30 31 is_anm_data_text = bpy.props.BoolProperty(name="Anm Text", default=True, description="Output Data to a JSON file") 32 33 remove_pre_animation = bpy.props.BoolProperty(name="既にあるアニメーションを削除", default=True) 34 set_frame = bpy.props.BoolProperty(name="フレーム開始・終了位置を調整", default=True) 35 ignore_automatic_bone = bpy.props.BoolProperty(name="Twisterボーンを除外", default=True) 36 37 is_location = bpy.props.BoolProperty(name="位置", default=True) 38 is_rotation = bpy.props.BoolProperty(name="回転", default=True) 39 is_scale = bpy.props.BoolProperty(name="拡縮", default=False) 40 is_tangents = bpy.props.BoolProperty(name="Tangents" , default=False) 41 42 @classmethod 43 def poll(cls, context): 44 ob = context.active_object 45 if ob and ob.type == 'ARMATURE': 46 return True 47 return False 48 49 def invoke(self, context, event): 50 prefs = common.preferences() 51 if prefs.anm_default_path: 52 self.filepath = common.default_cm3d2_dir(prefs.anm_default_path, None, "anm") 53 else: 54 self.filepath = common.default_cm3d2_dir(prefs.anm_import_path, None, "anm") 55 self.scale = prefs.scale 56 context.window_manager.fileselect_add(self) 57 return {'RUNNING_MODAL'} 58 59 def draw(self, context): 60 self.layout.prop(self, 'scale') 61 self.layout.prop(self, 'set_frame_rate' , icon=compat.icon('RENDER_ANIMATION')) 62 self.layout.prop(self, 'is_loop' , icon=compat.icon('LOOP_BACK' )) 63 self.layout.prop(self, 'is_anm_data_text', icon=compat.icon('TEXT' )) 64 65 box = self.layout.box() 66 box.prop(self, 'remove_pre_animation', icon='DISCLOSURE_TRI_DOWN') 67 box.prop(self, 'set_frame', icon='NEXT_KEYFRAME') 68 box.prop(self, 'ignore_automatic_bone', icon='X') 69 70 box = self.layout.box() 71 box.label(text="読み込むアニメーション情報") 72 column = box.column(align=True) 73 column.prop(self, 'is_location', icon=compat.icon('CON_LOCLIKE')) 74 column.prop(self, 'is_rotation', icon=compat.icon('CON_ROTLIKE')) 75 row = column.row() 76 row.prop(self, 'is_scale', icon=compat.icon('CON_SIZELIKE')) 77 row.enabled = False 78 column.prop(self, 'is_tangents', icon=compat.icon('IPO_BEZIER' )) 79 80 def execute(self, context): 81 prefs = common.preferences() 82 prefs.anm_import_path = self.filepath 83 prefs.scale = self.scale 84 85 try: 86 file = open(self.filepath, 'rb') 87 except: 88 self.report(type={'ERROR'}, message=f_tip_("ファイルを開くのに失敗しました、アクセス不可かファイルが存在しません。file={}", self.filepath)) 89 return {'CANCELLED'} 90 91 # ヘッダー 92 ext = common.read_str(file) 93 if ext != 'CM3D2_ANIM': 94 self.report(type={'ERROR'}, message="これはカスタムメイド3D2のモーションファイルではありません") 95 return {'CANCELLED'} 96 anm_version = struct.unpack('<i', file.read(4))[0] 97 first_channel_id = struct.unpack('<B', file.read(1))[0] 98 if first_channel_id != 1: 99 self.report(type={'ERROR'}, message=f_tip_("Unexpected first channel id = {id} (should be 1).", id=first_channel_id)) 100 return {'CANCELLED'} 101 102 103 anm_data = {} 104 for anim_data_index in range(9**9): 105 path = common.read_str(file) 106 107 base_bone_name = path.split('/')[-1] 108 if base_bone_name not in anm_data: 109 anm_data[base_bone_name] = {'path': path} 110 anm_data[base_bone_name]['channels'] = {} 111 112 for channel_index in range(9**9): 113 channel_id = struct.unpack('<B', file.read(1))[0] 114 channel_id_str = channel_id 115 if channel_id <= 1: 116 break 117 anm_data[base_bone_name]['channels'][channel_id_str] = [] 118 channel_data_count = struct.unpack('<i', file.read(4))[0] 119 for channel_data_index in range(channel_data_count): 120 frame = struct.unpack('<f', file.read(4))[0] 121 data = struct.unpack('<3f', file.read(4 * 3)) 122 123 anm_data[base_bone_name]['channels'][channel_id_str].append({'frame': frame, 'f0': data[0], 'f1': data[1], 'f2': data[2]}) 124 125 if channel_id == 0: 126 break 127 128 if self.is_anm_data_text: 129 if "AnmData" in context.blend_data.texts: 130 txt = context.blend_data.texts["AnmData"] 131 txt.clear() 132 else: 133 txt = context.blend_data.texts.new("AnmData") 134 import json 135 txt.write( json.dumps(anm_data, ensure_ascii=False, indent=2) ) 136 137 if self.set_frame_rate: 138 context.scene.render.fps = 60 139 fps = context.scene.render.fps 140 141 ob = context.active_object 142 arm = ob.data 143 pose = ob.pose 144 base_bone = arm.get('BaseBone') 145 if base_bone: 146 base_bone = arm.bones.get(base_bone) 147 148 anim = ob.animation_data 149 if not anim: 150 anim = ob.animation_data_create() 151 action = anim.action 152 if not action: 153 action = context.blend_data.actions.new(os.path.basename(self.filepath)) 154 anim.action = action 155 fcurves = action.fcurves 156 else: 157 action.name = os.path.basename(self.filepath) 158 fcurves = action.fcurves 159 if self.remove_pre_animation: 160 for fcurve in fcurves: 161 fcurves.remove(fcurve) 162 163 max_frame = 0 164 bpy.ops.object.mode_set(mode='OBJECT') 165 found_unknown = [] 166 found_tangents = [] 167 for bone_name, bone_data in anm_data.items(): 168 if self.ignore_automatic_bone: 169 if re.match(r"Kata_[RL]", bone_name): 170 continue 171 if re.match(r"Uppertwist1_[RL]", bone_name): 172 continue 173 if re.match(r"momoniku_[RL]", bone_name): 174 continue 175 176 if bone_name not in pose.bones: 177 bone_name = common.decode_bone_name(bone_name) 178 if bone_name not in pose.bones: 179 continue 180 bone = arm.bones[bone_name] 181 pose_bone = pose.bones[bone_name] 182 183 loc_fcurves = None 184 185 locs = {} 186 loc_tangents = {} 187 quats = {} 188 quat_tangents = {} 189 for channel_id, channel_data in bone_data['channels'].items(): 190 191 if channel_id in [100, 101, 102, 103]: 192 for data in channel_data: 193 frame = data['frame'] 194 if frame not in quats: 195 quats[frame] = [None, None, None, None] 196 197 if channel_id == 103: 198 quats[frame][0] = data['f0'] 199 elif channel_id == 100: 200 quats[frame][1] = data['f0'] 201 elif channel_id == 101: 202 quats[frame][2] = data['f0'] 203 elif channel_id == 102: 204 quats[frame][3] = data['f0'] 205 206 #tangents = (data['f1'], data['f2']) 207 #if (data['f1']**2 + data['f2']**2) ** .5 > 0.01: 208 # found_tangents.append(tangents) 209 if frame not in quat_tangents: 210 quat_tangents[frame] = {'in': [None, None, None, None], 'out': [None, None, None, None]} 211 212 if channel_id == 103: 213 quat_tangents[frame]['in' ][0] = data['f1'] 214 quat_tangents[frame]['out'][0] = data['f2'] 215 elif channel_id == 100: 216 quat_tangents[frame]['in' ][1] = data['f1'] 217 quat_tangents[frame]['out'][1] = data['f2'] 218 elif channel_id == 101: 219 quat_tangents[frame]['in' ][2] = data['f1'] 220 quat_tangents[frame]['out'][2] = data['f2'] 221 elif channel_id == 102: 222 quat_tangents[frame]['in' ][3] = data['f1'] 223 quat_tangents[frame]['out'][3] = data['f2'] 224 225 elif channel_id in [104, 105, 106]: 226 for data in channel_data: 227 frame = data['frame'] 228 if frame not in locs: 229 locs[frame] = [None, None, None] 230 231 if channel_id == 104: 232 locs[frame][0] = data['f0'] 233 elif channel_id == 105: 234 locs[frame][1] = data['f0'] 235 elif channel_id == 106: 236 locs[frame][2] = data['f0'] 237 238 #tangents = (data['f1'], data['f2']) 239 #if (data['f1']**2 + data['f2']**2) ** .5 > 0.05: 240 # found_tangents.append(tangents) 241 if frame not in loc_tangents: 242 loc_tangents[frame] = {'in': [None, None, None], 'out': [None, None, None]} 243 244 if channel_id == 104: 245 loc_tangents[frame]['in' ][0] = data['f1'] 246 loc_tangents[frame]['out'][0] = data['f2'] 247 elif channel_id == 105: 248 loc_tangents[frame]['in' ][1] = data['f1'] 249 loc_tangents[frame]['out'][1] = data['f2'] 250 elif channel_id == 106: 251 loc_tangents[frame]['in' ][2] = data['f1'] 252 loc_tangents[frame]['out'][2] = data['f2'] 253 254 elif channel_id not in found_unknown: 255 found_unknown.append(channel_id) 256 self.report(type={'INFO'}, message=f_tip_("Unknown channel id {num}", num=channel_id)) 257 258 ''' 259 for frame, (loc, quat) in enumerate(zip(locs.values(), quats.values())): 260 loc = mathutils.Vector(loc) * self.scale 261 quat = mathutils.Quaternion(quat) 262 263 loc_mat = mathutils.Matrix.Translation(loc).to_4x4() 264 rot_mat = quat.to_matrix().to_4x4() 265 mat = compat.mul(loc_mat, rot_mat) 266 267 bone_loc = bone.head_local.copy() 268 bone_quat = bone.matrix.to_quaternion() 269 270 if bone.parent: 271 parent = bone.parent 272 else: 273 parent = base_bone 274 275 if parent: 276 mat = compat.convert_cm_to_bl_bone_space(mat) 277 mat = compat.mul(parent.matrix_local, mat) 278 mat = compat.convert_cm_to_bl_bone_rotation(mat) 279 pose_mat = bone.convert_local_to_pose( 280 matrix = mat, 281 matrix_local = bone.matrix_local, 282 parent_matrix = mathutils.Matrix.Identity(4), 283 parent_matrix_local = parent.matrix_local 284 ) 285 else: 286 mat = compat.convert_cm_to_bl_bone_rotation(mat) 287 mat = compat.convert_cm_to_bl_space(mat) 288 pose_mat = bone.convert_local_to_pose( 289 matrix = mat, 290 matrix_local = bone.matrix_local 291 ) 292 293 if self.is_location: 294 pose_bone.location = pose_mat.to_translation() 295 pose_bone.keyframe_insert('location' , frame=frame * fps, group=pose_bone.name) 296 if self.is_rotation: 297 pose_bone.rotation_quaternion = pose_mat.to_quaternion() 298 pose_bone.keyframe_insert('rotation_quaternion', frame=frame * fps, group=pose_bone.name) 299 if max_frame < frame * fps: 300 max_frame = frame * fps 301 ''' 302 303 def _apply_tangents(fcurves, keyframes, tangents): 304 for axis_index, axis_keyframes in enumerate(keyframes): 305 fcurve = fcurves[axis_index] 306 fcurve.update() # make sure automatic handles are calculated 307 axis_keyframes.sort() # make sure list is in order 308 for keyframe_index, frame in enumerate(axis_keyframes): 309 tangent_in = tangents[frame]['in' ][axis_index] 310 tangent_out = tangents[frame]['out'][axis_index] 311 312 vec_in = mathutils.Vector((1, tangent_in / fps)) 313 vec_out = mathutils.Vector((1, tangent_out / fps)) 314 315 this_keyframe = fcurve.keyframe_points[keyframe_index ] 316 next_keyframe = fcurve.keyframe_points[keyframe_index+1] if keyframe_index+1 < len(axis_keyframes) else None 317 last_keyframe = fcurve.keyframe_points[keyframe_index-1] if keyframe_index-1 >= 0 else None 318 319 if vec_in.y != vec_out.y: 320 this_keyframe.handle_left_type = 'FREE' 321 this_keyframe.handle_right_type = 'FREE' 322 else: 323 this_keyframe.handle_left_type = 'ALIGNED' 324 this_keyframe.handle_right_type = 'ALIGNED' 325 326 this_co = mathutils.Vector(this_keyframe.co) 327 next_co = mathutils.Vector(next_keyframe.co) if next_keyframe else None 328 last_co = mathutils.Vector(last_keyframe.co) if last_keyframe else None 329 if not next_keyframe: 330 next_keyframe = fcurve.keyframe_points[0] 331 if next_keyframe and next_keyframe != this_keyframe: 332 next_co = mathutils.Vector(next_keyframe.co) 333 next_co.x += max_frame 334 if not last_keyframe: 335 last_keyframe = fcurve.keyframe_points[len(axis_keyframes)-1] 336 if last_keyframe and last_keyframe != this_keyframe: 337 last_co = mathutils.Vector(last_keyframe.co) 338 last_co.x -= max_frame 339 340 factor = 3 341 dist_in = (last_co.x - this_co.x) / factor if factor and last_co else None 342 dist_out = (next_co.x - this_co.x) / factor if factor and next_co else None 343 if not dist_in and not dist_out: 344 dist_in = this_keyframe.handle_left[0] - this_co.x 345 dist_out = this_keyframe.handle_right[0] - this_co.x 346 elif not dist_in: 347 dist_in = -dist_out 348 elif not dist_out: 349 dist_out = -dist_in 350 351 this_keyframe.handle_left = vec_in * dist_in + this_co 352 this_keyframe.handle_right = vec_out * dist_out + this_co 353 354 355 if self.is_location: 356 loc_fcurves = [None, None, None] 357 loc_keyframes = [[],[],[]] 358 rna_data_path = 'pose.bones["{bone_name}"].location'.format(bone_name=bone.name) 359 for axis_index in range(0, 3): 360 new_fcurve = fcurves.find(rna_data_path, index=axis_index) 361 if not new_fcurve: 362 new_fcurve = fcurves.new(rna_data_path, index=axis_index, action_group=pose_bone.name) 363 loc_fcurves[axis_index] = new_fcurve 364 365 def _convert_loc(loc) -> mathutils.Vector: 366 loc = mathutils.Vector(loc) * self.scale 367 #bone_loc = bone.head_local.copy() 368 # 369 #if bone.parent: 370 # #loc.x, loc.y, loc.z = -loc.y, -loc.x, loc.z 371 # 372 # #co.x, co.y, co.z = -co.y, co.z, co.x 373 # #loc.x, loc.y, loc.z = loc.z, -loc.x, loc.y 374 # #mat = mathutils.Matrix( 375 # # [( 0, 0, 1, 0), 376 # # (-1, 0, 0, 0), 377 # # ( 0, 1, 0, 0), 378 # # ( 0, 0, 0, 1)] 379 # #) 380 # #loc = compat.mul(mat, loc) 381 # 382 # loc = compat.convert_cm_to_bl_bone_space(loc) 383 # 384 # bone_loc = bone_loc - bone.parent.head_local 385 # bone_loc.rotate(bone.parent.matrix_local.to_quaternion().inverted()) 386 #else: 387 # #loc.x, loc.y, loc.z = loc.x, loc.z, loc.y 388 # loc = compat.convert_cm_to_bl_space(loc) 389 # 390 #result_loc = loc - bone_loc 391 if bone.parent: 392 loc = compat.convert_cm_to_bl_bone_space(loc) 393 loc = compat.mul(bone.parent.matrix_local, loc) 394 else: 395 loc = compat.convert_cm_to_bl_space(loc) 396 return compat.mul(bone.matrix_local.inverted(), loc) 397 398 for frame, loc in locs.items(): 399 result_loc = _convert_loc(loc) 400 #pose_bone.location = result_loc 401 402 #pose_bone.keyframe_insert('location', frame=frame * fps, group=pose_bone.name) 403 if max_frame < frame * fps: 404 max_frame = frame * fps 405 406 for fcurve in loc_fcurves: 407 keyframe_type = 'KEYFRAME' 408 tangents = loc_tangents[frame] 409 if tangents: 410 tangents = mathutils.Vector((tangents['in'][fcurve.array_index], tangents['out'][fcurve.array_index])) 411 if tangents.magnitude < 1e-6: 412 keyframe_type = 'JITTER' 413 elif tangents.magnitude > 0.1: 414 keyframe_type = 'EXTREME' 415 416 keyframe = fcurve.keyframe_points.insert( 417 frame = frame * fps , 418 value = result_loc[fcurve.array_index], 419 options = {'FAST'} , 420 keyframe_type = keyframe_type 421 ) 422 keyframe.type = keyframe_type 423 loc_keyframes[fcurve.array_index].append(frame) 424 425 if self.is_loop: 426 for fcurve in loc_fcurves: 427 new_modifier = fcurve.modifiers.new('CYCLES') 428 429 if self.is_tangents: 430 for frame, tangents in loc_tangents.items(): 431 tangent_in = mathutils.Vector(tangents['in' ]) * self.scale 432 tangent_out = mathutils.Vector(tangents['out']) * self.scale 433 if bone.parent: 434 tangent_in = compat.convert_cm_to_bl_bone_space(tangent_in ) 435 tangent_out = compat.convert_cm_to_bl_bone_space(tangent_out) 436 else: 437 tangent_in = compat.convert_cm_to_bl_space(tangent_in ) 438 tangent_out = compat.convert_cm_to_bl_space(tangent_out) 439 tangents['in' ][:] = tangent_in [:] 440 tangents['out'][:] = tangent_out[:] 441 442 _apply_tangents(loc_fcurves, loc_keyframes, loc_tangents) 443 444 445 446 if self.is_rotation: 447 quat_fcurves = [None, None, None, None] 448 quat_keyframes = [[],[],[],[]] 449 rna_data_path = 'pose.bones["{bone_name}"].rotation_quaternion'.format(bone_name=pose_bone.name) 450 for axis_index in range(0, 4): 451 new_fcurve = fcurves.find(rna_data_path, index=axis_index) 452 if not new_fcurve: 453 new_fcurve = fcurves.new(rna_data_path, index=axis_index, action_group=pose_bone.name) 454 quat_fcurves[axis_index] = new_fcurve 455 456 457 bone_quat = bone.matrix.to_quaternion() 458 def _convert_quat(quat) -> mathutils.Quaternion: 459 quat = mathutils.Quaternion(quat) 460 #orig_quat = quat.copy() 461 '''Can't use matrix transforms here as they would mess up interpolation.''' 462 if bone.parent: 463 quat.w, quat.x, quat.y, quat.z = quat.w, -quat.z, quat.x, -quat.y 464 #quat_mat = compat.convert_cm_to_bl_bone_space(quat.to_matrix().to_4x4()) 465 #quat_mat = compat.convert_cm_to_bl_bone_rotation(quat_mat) 466 else: 467 quat.w, quat.x, quat.y, quat.z = quat.w, -quat.z, quat.x, -quat.y 468 quat = compat.mul(mathutils.Matrix.Rotation(math.radians(-90.0), 4, 'Z').to_quaternion(), quat) 469 #quat_mat = compat.convert_cm_to_bl_space(quat.to_matrix().to_4x4()) 470 #quat = compat.convert_cm_to_bl_bone_rotation(quat_mat).to_quaternion() 471 quat = compat.mul(bone_quat.inverted(), quat) 472 #quat.make_compatible(orig_quat) 473 return quat 474 475 for frame, quat in quats.items(): 476 result_quat = _convert_quat(quat) 477 #pose_bone.rotation_quaternion = result_quat.copy() 478 479 #pose_bone.keyframe_insert('rotation_quaternion', frame=frame * fps, group=pose_bone.name) 480 if max_frame < frame * fps: 481 max_frame = frame * fps 482 483 for fcurve in quat_fcurves: 484 keyframe_type = 'KEYFRAME' 485 tangents = quat_tangents[frame] 486 if tangents: 487 tangents = mathutils.Vector((tangents['in'][fcurve.array_index], tangents['out'][fcurve.array_index])) 488 if tangents.magnitude < 1e-6: 489 keyframe_type = 'JITTER' 490 elif tangents.magnitude > 0.1: 491 keyframe_type = 'EXTREME' 492 493 keyframe = fcurve.keyframe_points.insert( 494 frame = frame * fps , 495 value = result_quat[fcurve.array_index] , 496 options = {'FAST'} , 497 keyframe_type = keyframe_type 498 ) 499 keyframe.type = keyframe_type 500 quat_keyframes[fcurve.array_index].append(frame) 501 502 if self.is_loop: 503 for fcurve in quat_fcurves: 504 new_modifier = fcurve.modifiers.new('CYCLES') 505 506 if self.is_tangents: 507 for frame, tangents in quat_tangents.items(): 508 tangents['in' ][:] = _convert_quat(tangents['in' ])[:] 509 tangents['out'][:] = _convert_quat(tangents['out'])[:] 510 511 _apply_tangents(quat_fcurves, quat_keyframes, quat_tangents) 512 513 514 515 if found_tangents: 516 self.report(type={'INFO'}, message="Found the following tangent values:") 517 for f1, f2 in found_tangents: 518 self.report(type={'INFO'}, message=f_tip_("f1 = {float1}, f2 = {float2}", float1=f1, float2=f2)) 519 self.report(type={'INFO'}, message="Found the above tangent values.") 520 self.report(type={'WARNING'}, message=f_tip_("Found {count} large tangents. Blender animation may not interpolate properly. See log for more info.", count=len(found_tangents))) 521 if found_unknown: 522 self.report(type={'INFO'}, message="Found the following unknown channel IDs:") 523 for channel_id in found_unknown: 524 self.report(type={'INFO'}, message=f_tip_("id = {id}", id=channel_id)) 525 self.report(type={'INFO'}, message="Found the above unknown channel IDs.") 526 self.report(type={'WARNING'}, message=f_tip_("Found {count} unknown channel IDs. Blender animation may be missing some keyframes. See log for more info.", count=len(found_unknown))) 527 528 if self.set_frame: 529 context.scene.frame_start = 0 530 context.scene.frame_end = max_frame 531 context.scene.frame_set(0) 532 533 return {'FINISHED'} 534 535 536# メニューに登録する関数 537def menu_func(self, context): 538 self.layout.operator(CNV_OT_import_cm3d2_anm.bl_idname, icon_value=common.kiss_icon())
@compat.BlRegister()
class
CNV_OT_import_cm3d2_anm17@compat.BlRegister() 18class CNV_OT_import_cm3d2_anm(bpy.types.Operator): 19 bl_idname = 'import_anim.import_cm3d2_anm' 20 bl_label = "CM3D2モーション (.anm)" 21 bl_description = "カスタムメイド3D2のanmファイルを読み込みます" 22 bl_options = {'REGISTER'} 23 24 filepath = bpy.props.StringProperty(subtype='FILE_PATH') 25 filename_ext = ".anm" 26 filter_glob = bpy.props.StringProperty(default="*.anm", options={'HIDDEN'}) 27 28 scale = bpy.props.FloatProperty(name="倍率", default=5, min=0.1, max=100, soft_min=0.1, soft_max=100, step=100, precision=1, description="インポート時のメッシュ等の拡大率です") 29 set_frame_rate = bpy.props.BoolProperty(name="Set Framerate", default=True, description="Change the scene's render settings to 60 fps") 30 is_loop = bpy.props.BoolProperty(name="Loop", default=True) 31 32 is_anm_data_text = bpy.props.BoolProperty(name="Anm Text", default=True, description="Output Data to a JSON file") 33 34 remove_pre_animation = bpy.props.BoolProperty(name="既にあるアニメーションを削除", default=True) 35 set_frame = bpy.props.BoolProperty(name="フレーム開始・終了位置を調整", default=True) 36 ignore_automatic_bone = bpy.props.BoolProperty(name="Twisterボーンを除外", default=True) 37 38 is_location = bpy.props.BoolProperty(name="位置", default=True) 39 is_rotation = bpy.props.BoolProperty(name="回転", default=True) 40 is_scale = bpy.props.BoolProperty(name="拡縮", default=False) 41 is_tangents = bpy.props.BoolProperty(name="Tangents" , default=False) 42 43 @classmethod 44 def poll(cls, context): 45 ob = context.active_object 46 if ob and ob.type == 'ARMATURE': 47 return True 48 return False 49 50 def invoke(self, context, event): 51 prefs = common.preferences() 52 if prefs.anm_default_path: 53 self.filepath = common.default_cm3d2_dir(prefs.anm_default_path, None, "anm") 54 else: 55 self.filepath = common.default_cm3d2_dir(prefs.anm_import_path, None, "anm") 56 self.scale = prefs.scale 57 context.window_manager.fileselect_add(self) 58 return {'RUNNING_MODAL'} 59 60 def draw(self, context): 61 self.layout.prop(self, 'scale') 62 self.layout.prop(self, 'set_frame_rate' , icon=compat.icon('RENDER_ANIMATION')) 63 self.layout.prop(self, 'is_loop' , icon=compat.icon('LOOP_BACK' )) 64 self.layout.prop(self, 'is_anm_data_text', icon=compat.icon('TEXT' )) 65 66 box = self.layout.box() 67 box.prop(self, 'remove_pre_animation', icon='DISCLOSURE_TRI_DOWN') 68 box.prop(self, 'set_frame', icon='NEXT_KEYFRAME') 69 box.prop(self, 'ignore_automatic_bone', icon='X') 70 71 box = self.layout.box() 72 box.label(text="読み込むアニメーション情報") 73 column = box.column(align=True) 74 column.prop(self, 'is_location', icon=compat.icon('CON_LOCLIKE')) 75 column.prop(self, 'is_rotation', icon=compat.icon('CON_ROTLIKE')) 76 row = column.row() 77 row.prop(self, 'is_scale', icon=compat.icon('CON_SIZELIKE')) 78 row.enabled = False 79 column.prop(self, 'is_tangents', icon=compat.icon('IPO_BEZIER' )) 80 81 def execute(self, context): 82 prefs = common.preferences() 83 prefs.anm_import_path = self.filepath 84 prefs.scale = self.scale 85 86 try: 87 file = open(self.filepath, 'rb') 88 except: 89 self.report(type={'ERROR'}, message=f_tip_("ファイルを開くのに失敗しました、アクセス不可かファイルが存在しません。file={}", self.filepath)) 90 return {'CANCELLED'} 91 92 # ヘッダー 93 ext = common.read_str(file) 94 if ext != 'CM3D2_ANIM': 95 self.report(type={'ERROR'}, message="これはカスタムメイド3D2のモーションファイルではありません") 96 return {'CANCELLED'} 97 anm_version = struct.unpack('<i', file.read(4))[0] 98 first_channel_id = struct.unpack('<B', file.read(1))[0] 99 if first_channel_id != 1: 100 self.report(type={'ERROR'}, message=f_tip_("Unexpected first channel id = {id} (should be 1).", id=first_channel_id)) 101 return {'CANCELLED'} 102 103 104 anm_data = {} 105 for anim_data_index in range(9**9): 106 path = common.read_str(file) 107 108 base_bone_name = path.split('/')[-1] 109 if base_bone_name not in anm_data: 110 anm_data[base_bone_name] = {'path': path} 111 anm_data[base_bone_name]['channels'] = {} 112 113 for channel_index in range(9**9): 114 channel_id = struct.unpack('<B', file.read(1))[0] 115 channel_id_str = channel_id 116 if channel_id <= 1: 117 break 118 anm_data[base_bone_name]['channels'][channel_id_str] = [] 119 channel_data_count = struct.unpack('<i', file.read(4))[0] 120 for channel_data_index in range(channel_data_count): 121 frame = struct.unpack('<f', file.read(4))[0] 122 data = struct.unpack('<3f', file.read(4 * 3)) 123 124 anm_data[base_bone_name]['channels'][channel_id_str].append({'frame': frame, 'f0': data[0], 'f1': data[1], 'f2': data[2]}) 125 126 if channel_id == 0: 127 break 128 129 if self.is_anm_data_text: 130 if "AnmData" in context.blend_data.texts: 131 txt = context.blend_data.texts["AnmData"] 132 txt.clear() 133 else: 134 txt = context.blend_data.texts.new("AnmData") 135 import json 136 txt.write( json.dumps(anm_data, ensure_ascii=False, indent=2) ) 137 138 if self.set_frame_rate: 139 context.scene.render.fps = 60 140 fps = context.scene.render.fps 141 142 ob = context.active_object 143 arm = ob.data 144 pose = ob.pose 145 base_bone = arm.get('BaseBone') 146 if base_bone: 147 base_bone = arm.bones.get(base_bone) 148 149 anim = ob.animation_data 150 if not anim: 151 anim = ob.animation_data_create() 152 action = anim.action 153 if not action: 154 action = context.blend_data.actions.new(os.path.basename(self.filepath)) 155 anim.action = action 156 fcurves = action.fcurves 157 else: 158 action.name = os.path.basename(self.filepath) 159 fcurves = action.fcurves 160 if self.remove_pre_animation: 161 for fcurve in fcurves: 162 fcurves.remove(fcurve) 163 164 max_frame = 0 165 bpy.ops.object.mode_set(mode='OBJECT') 166 found_unknown = [] 167 found_tangents = [] 168 for bone_name, bone_data in anm_data.items(): 169 if self.ignore_automatic_bone: 170 if re.match(r"Kata_[RL]", bone_name): 171 continue 172 if re.match(r"Uppertwist1_[RL]", bone_name): 173 continue 174 if re.match(r"momoniku_[RL]", bone_name): 175 continue 176 177 if bone_name not in pose.bones: 178 bone_name = common.decode_bone_name(bone_name) 179 if bone_name not in pose.bones: 180 continue 181 bone = arm.bones[bone_name] 182 pose_bone = pose.bones[bone_name] 183 184 loc_fcurves = None 185 186 locs = {} 187 loc_tangents = {} 188 quats = {} 189 quat_tangents = {} 190 for channel_id, channel_data in bone_data['channels'].items(): 191 192 if channel_id in [100, 101, 102, 103]: 193 for data in channel_data: 194 frame = data['frame'] 195 if frame not in quats: 196 quats[frame] = [None, None, None, None] 197 198 if channel_id == 103: 199 quats[frame][0] = data['f0'] 200 elif channel_id == 100: 201 quats[frame][1] = data['f0'] 202 elif channel_id == 101: 203 quats[frame][2] = data['f0'] 204 elif channel_id == 102: 205 quats[frame][3] = data['f0'] 206 207 #tangents = (data['f1'], data['f2']) 208 #if (data['f1']**2 + data['f2']**2) ** .5 > 0.01: 209 # found_tangents.append(tangents) 210 if frame not in quat_tangents: 211 quat_tangents[frame] = {'in': [None, None, None, None], 'out': [None, None, None, None]} 212 213 if channel_id == 103: 214 quat_tangents[frame]['in' ][0] = data['f1'] 215 quat_tangents[frame]['out'][0] = data['f2'] 216 elif channel_id == 100: 217 quat_tangents[frame]['in' ][1] = data['f1'] 218 quat_tangents[frame]['out'][1] = data['f2'] 219 elif channel_id == 101: 220 quat_tangents[frame]['in' ][2] = data['f1'] 221 quat_tangents[frame]['out'][2] = data['f2'] 222 elif channel_id == 102: 223 quat_tangents[frame]['in' ][3] = data['f1'] 224 quat_tangents[frame]['out'][3] = data['f2'] 225 226 elif channel_id in [104, 105, 106]: 227 for data in channel_data: 228 frame = data['frame'] 229 if frame not in locs: 230 locs[frame] = [None, None, None] 231 232 if channel_id == 104: 233 locs[frame][0] = data['f0'] 234 elif channel_id == 105: 235 locs[frame][1] = data['f0'] 236 elif channel_id == 106: 237 locs[frame][2] = data['f0'] 238 239 #tangents = (data['f1'], data['f2']) 240 #if (data['f1']**2 + data['f2']**2) ** .5 > 0.05: 241 # found_tangents.append(tangents) 242 if frame not in loc_tangents: 243 loc_tangents[frame] = {'in': [None, None, None], 'out': [None, None, None]} 244 245 if channel_id == 104: 246 loc_tangents[frame]['in' ][0] = data['f1'] 247 loc_tangents[frame]['out'][0] = data['f2'] 248 elif channel_id == 105: 249 loc_tangents[frame]['in' ][1] = data['f1'] 250 loc_tangents[frame]['out'][1] = data['f2'] 251 elif channel_id == 106: 252 loc_tangents[frame]['in' ][2] = data['f1'] 253 loc_tangents[frame]['out'][2] = data['f2'] 254 255 elif channel_id not in found_unknown: 256 found_unknown.append(channel_id) 257 self.report(type={'INFO'}, message=f_tip_("Unknown channel id {num}", num=channel_id)) 258 259 ''' 260 for frame, (loc, quat) in enumerate(zip(locs.values(), quats.values())): 261 loc = mathutils.Vector(loc) * self.scale 262 quat = mathutils.Quaternion(quat) 263 264 loc_mat = mathutils.Matrix.Translation(loc).to_4x4() 265 rot_mat = quat.to_matrix().to_4x4() 266 mat = compat.mul(loc_mat, rot_mat) 267 268 bone_loc = bone.head_local.copy() 269 bone_quat = bone.matrix.to_quaternion() 270 271 if bone.parent: 272 parent = bone.parent 273 else: 274 parent = base_bone 275 276 if parent: 277 mat = compat.convert_cm_to_bl_bone_space(mat) 278 mat = compat.mul(parent.matrix_local, mat) 279 mat = compat.convert_cm_to_bl_bone_rotation(mat) 280 pose_mat = bone.convert_local_to_pose( 281 matrix = mat, 282 matrix_local = bone.matrix_local, 283 parent_matrix = mathutils.Matrix.Identity(4), 284 parent_matrix_local = parent.matrix_local 285 ) 286 else: 287 mat = compat.convert_cm_to_bl_bone_rotation(mat) 288 mat = compat.convert_cm_to_bl_space(mat) 289 pose_mat = bone.convert_local_to_pose( 290 matrix = mat, 291 matrix_local = bone.matrix_local 292 ) 293 294 if self.is_location: 295 pose_bone.location = pose_mat.to_translation() 296 pose_bone.keyframe_insert('location' , frame=frame * fps, group=pose_bone.name) 297 if self.is_rotation: 298 pose_bone.rotation_quaternion = pose_mat.to_quaternion() 299 pose_bone.keyframe_insert('rotation_quaternion', frame=frame * fps, group=pose_bone.name) 300 if max_frame < frame * fps: 301 max_frame = frame * fps 302 ''' 303 304 def _apply_tangents(fcurves, keyframes, tangents): 305 for axis_index, axis_keyframes in enumerate(keyframes): 306 fcurve = fcurves[axis_index] 307 fcurve.update() # make sure automatic handles are calculated 308 axis_keyframes.sort() # make sure list is in order 309 for keyframe_index, frame in enumerate(axis_keyframes): 310 tangent_in = tangents[frame]['in' ][axis_index] 311 tangent_out = tangents[frame]['out'][axis_index] 312 313 vec_in = mathutils.Vector((1, tangent_in / fps)) 314 vec_out = mathutils.Vector((1, tangent_out / fps)) 315 316 this_keyframe = fcurve.keyframe_points[keyframe_index ] 317 next_keyframe = fcurve.keyframe_points[keyframe_index+1] if keyframe_index+1 < len(axis_keyframes) else None 318 last_keyframe = fcurve.keyframe_points[keyframe_index-1] if keyframe_index-1 >= 0 else None 319 320 if vec_in.y != vec_out.y: 321 this_keyframe.handle_left_type = 'FREE' 322 this_keyframe.handle_right_type = 'FREE' 323 else: 324 this_keyframe.handle_left_type = 'ALIGNED' 325 this_keyframe.handle_right_type = 'ALIGNED' 326 327 this_co = mathutils.Vector(this_keyframe.co) 328 next_co = mathutils.Vector(next_keyframe.co) if next_keyframe else None 329 last_co = mathutils.Vector(last_keyframe.co) if last_keyframe else None 330 if not next_keyframe: 331 next_keyframe = fcurve.keyframe_points[0] 332 if next_keyframe and next_keyframe != this_keyframe: 333 next_co = mathutils.Vector(next_keyframe.co) 334 next_co.x += max_frame 335 if not last_keyframe: 336 last_keyframe = fcurve.keyframe_points[len(axis_keyframes)-1] 337 if last_keyframe and last_keyframe != this_keyframe: 338 last_co = mathutils.Vector(last_keyframe.co) 339 last_co.x -= max_frame 340 341 factor = 3 342 dist_in = (last_co.x - this_co.x) / factor if factor and last_co else None 343 dist_out = (next_co.x - this_co.x) / factor if factor and next_co else None 344 if not dist_in and not dist_out: 345 dist_in = this_keyframe.handle_left[0] - this_co.x 346 dist_out = this_keyframe.handle_right[0] - this_co.x 347 elif not dist_in: 348 dist_in = -dist_out 349 elif not dist_out: 350 dist_out = -dist_in 351 352 this_keyframe.handle_left = vec_in * dist_in + this_co 353 this_keyframe.handle_right = vec_out * dist_out + this_co 354 355 356 if self.is_location: 357 loc_fcurves = [None, None, None] 358 loc_keyframes = [[],[],[]] 359 rna_data_path = 'pose.bones["{bone_name}"].location'.format(bone_name=bone.name) 360 for axis_index in range(0, 3): 361 new_fcurve = fcurves.find(rna_data_path, index=axis_index) 362 if not new_fcurve: 363 new_fcurve = fcurves.new(rna_data_path, index=axis_index, action_group=pose_bone.name) 364 loc_fcurves[axis_index] = new_fcurve 365 366 def _convert_loc(loc) -> mathutils.Vector: 367 loc = mathutils.Vector(loc) * self.scale 368 #bone_loc = bone.head_local.copy() 369 # 370 #if bone.parent: 371 # #loc.x, loc.y, loc.z = -loc.y, -loc.x, loc.z 372 # 373 # #co.x, co.y, co.z = -co.y, co.z, co.x 374 # #loc.x, loc.y, loc.z = loc.z, -loc.x, loc.y 375 # #mat = mathutils.Matrix( 376 # # [( 0, 0, 1, 0), 377 # # (-1, 0, 0, 0), 378 # # ( 0, 1, 0, 0), 379 # # ( 0, 0, 0, 1)] 380 # #) 381 # #loc = compat.mul(mat, loc) 382 # 383 # loc = compat.convert_cm_to_bl_bone_space(loc) 384 # 385 # bone_loc = bone_loc - bone.parent.head_local 386 # bone_loc.rotate(bone.parent.matrix_local.to_quaternion().inverted()) 387 #else: 388 # #loc.x, loc.y, loc.z = loc.x, loc.z, loc.y 389 # loc = compat.convert_cm_to_bl_space(loc) 390 # 391 #result_loc = loc - bone_loc 392 if bone.parent: 393 loc = compat.convert_cm_to_bl_bone_space(loc) 394 loc = compat.mul(bone.parent.matrix_local, loc) 395 else: 396 loc = compat.convert_cm_to_bl_space(loc) 397 return compat.mul(bone.matrix_local.inverted(), loc) 398 399 for frame, loc in locs.items(): 400 result_loc = _convert_loc(loc) 401 #pose_bone.location = result_loc 402 403 #pose_bone.keyframe_insert('location', frame=frame * fps, group=pose_bone.name) 404 if max_frame < frame * fps: 405 max_frame = frame * fps 406 407 for fcurve in loc_fcurves: 408 keyframe_type = 'KEYFRAME' 409 tangents = loc_tangents[frame] 410 if tangents: 411 tangents = mathutils.Vector((tangents['in'][fcurve.array_index], tangents['out'][fcurve.array_index])) 412 if tangents.magnitude < 1e-6: 413 keyframe_type = 'JITTER' 414 elif tangents.magnitude > 0.1: 415 keyframe_type = 'EXTREME' 416 417 keyframe = fcurve.keyframe_points.insert( 418 frame = frame * fps , 419 value = result_loc[fcurve.array_index], 420 options = {'FAST'} , 421 keyframe_type = keyframe_type 422 ) 423 keyframe.type = keyframe_type 424 loc_keyframes[fcurve.array_index].append(frame) 425 426 if self.is_loop: 427 for fcurve in loc_fcurves: 428 new_modifier = fcurve.modifiers.new('CYCLES') 429 430 if self.is_tangents: 431 for frame, tangents in loc_tangents.items(): 432 tangent_in = mathutils.Vector(tangents['in' ]) * self.scale 433 tangent_out = mathutils.Vector(tangents['out']) * self.scale 434 if bone.parent: 435 tangent_in = compat.convert_cm_to_bl_bone_space(tangent_in ) 436 tangent_out = compat.convert_cm_to_bl_bone_space(tangent_out) 437 else: 438 tangent_in = compat.convert_cm_to_bl_space(tangent_in ) 439 tangent_out = compat.convert_cm_to_bl_space(tangent_out) 440 tangents['in' ][:] = tangent_in [:] 441 tangents['out'][:] = tangent_out[:] 442 443 _apply_tangents(loc_fcurves, loc_keyframes, loc_tangents) 444 445 446 447 if self.is_rotation: 448 quat_fcurves = [None, None, None, None] 449 quat_keyframes = [[],[],[],[]] 450 rna_data_path = 'pose.bones["{bone_name}"].rotation_quaternion'.format(bone_name=pose_bone.name) 451 for axis_index in range(0, 4): 452 new_fcurve = fcurves.find(rna_data_path, index=axis_index) 453 if not new_fcurve: 454 new_fcurve = fcurves.new(rna_data_path, index=axis_index, action_group=pose_bone.name) 455 quat_fcurves[axis_index] = new_fcurve 456 457 458 bone_quat = bone.matrix.to_quaternion() 459 def _convert_quat(quat) -> mathutils.Quaternion: 460 quat = mathutils.Quaternion(quat) 461 #orig_quat = quat.copy() 462 '''Can't use matrix transforms here as they would mess up interpolation.''' 463 if bone.parent: 464 quat.w, quat.x, quat.y, quat.z = quat.w, -quat.z, quat.x, -quat.y 465 #quat_mat = compat.convert_cm_to_bl_bone_space(quat.to_matrix().to_4x4()) 466 #quat_mat = compat.convert_cm_to_bl_bone_rotation(quat_mat) 467 else: 468 quat.w, quat.x, quat.y, quat.z = quat.w, -quat.z, quat.x, -quat.y 469 quat = compat.mul(mathutils.Matrix.Rotation(math.radians(-90.0), 4, 'Z').to_quaternion(), quat) 470 #quat_mat = compat.convert_cm_to_bl_space(quat.to_matrix().to_4x4()) 471 #quat = compat.convert_cm_to_bl_bone_rotation(quat_mat).to_quaternion() 472 quat = compat.mul(bone_quat.inverted(), quat) 473 #quat.make_compatible(orig_quat) 474 return quat 475 476 for frame, quat in quats.items(): 477 result_quat = _convert_quat(quat) 478 #pose_bone.rotation_quaternion = result_quat.copy() 479 480 #pose_bone.keyframe_insert('rotation_quaternion', frame=frame * fps, group=pose_bone.name) 481 if max_frame < frame * fps: 482 max_frame = frame * fps 483 484 for fcurve in quat_fcurves: 485 keyframe_type = 'KEYFRAME' 486 tangents = quat_tangents[frame] 487 if tangents: 488 tangents = mathutils.Vector((tangents['in'][fcurve.array_index], tangents['out'][fcurve.array_index])) 489 if tangents.magnitude < 1e-6: 490 keyframe_type = 'JITTER' 491 elif tangents.magnitude > 0.1: 492 keyframe_type = 'EXTREME' 493 494 keyframe = fcurve.keyframe_points.insert( 495 frame = frame * fps , 496 value = result_quat[fcurve.array_index] , 497 options = {'FAST'} , 498 keyframe_type = keyframe_type 499 ) 500 keyframe.type = keyframe_type 501 quat_keyframes[fcurve.array_index].append(frame) 502 503 if self.is_loop: 504 for fcurve in quat_fcurves: 505 new_modifier = fcurve.modifiers.new('CYCLES') 506 507 if self.is_tangents: 508 for frame, tangents in quat_tangents.items(): 509 tangents['in' ][:] = _convert_quat(tangents['in' ])[:] 510 tangents['out'][:] = _convert_quat(tangents['out'])[:] 511 512 _apply_tangents(quat_fcurves, quat_keyframes, quat_tangents) 513 514 515 516 if found_tangents: 517 self.report(type={'INFO'}, message="Found the following tangent values:") 518 for f1, f2 in found_tangents: 519 self.report(type={'INFO'}, message=f_tip_("f1 = {float1}, f2 = {float2}", float1=f1, float2=f2)) 520 self.report(type={'INFO'}, message="Found the above tangent values.") 521 self.report(type={'WARNING'}, message=f_tip_("Found {count} large tangents. Blender animation may not interpolate properly. See log for more info.", count=len(found_tangents))) 522 if found_unknown: 523 self.report(type={'INFO'}, message="Found the following unknown channel IDs:") 524 for channel_id in found_unknown: 525 self.report(type={'INFO'}, message=f_tip_("id = {id}", id=channel_id)) 526 self.report(type={'INFO'}, message="Found the above unknown channel IDs.") 527 self.report(type={'WARNING'}, message=f_tip_("Found {count} unknown channel IDs. Blender animation may be missing some keyframes. See log for more info.", count=len(found_unknown))) 528 529 if self.set_frame: 530 context.scene.frame_start = 0 531 context.scene.frame_end = max_frame 532 context.scene.frame_set(0) 533 534 return {'FINISHED'}
filepath: <_PropertyDeferred, <built-in function StringProperty>, {'subtype': 'FILE_PATH', 'attr': 'filepath'}> =
<_PropertyDeferred, <built-in function StringProperty>, {'subtype': 'FILE_PATH', 'attr': 'filepath'}>
filter_glob: <_PropertyDeferred, <built-in function StringProperty>, {'default': '*.anm', 'options': {'HIDDEN'}, 'attr': 'filter_glob'}> =
<_PropertyDeferred, <built-in function StringProperty>, {'default': '*.anm', 'options': {'HIDDEN'}, 'attr': 'filter_glob'}>
scale: <_PropertyDeferred, <built-in function FloatProperty>, {'name': '倍率', 'default': 5, 'min': 0.1, 'max': 100, 'soft_min': 0.1, 'soft_max': 100, 'step': 100, 'precision': 1, 'description': 'インポート時のメッシュ等の拡大率です', 'attr': 'scale'}> =
<_PropertyDeferred, <built-in function FloatProperty>, {'name': '倍率', 'default': 5, 'min': 0.1, 'max': 100, 'soft_min': 0.1, 'soft_max': 100, 'step': 100, 'precision': 1, 'description': 'インポート時のメッシュ等の拡大率です', 'attr': 'scale'}>
set_frame_rate: <_PropertyDeferred, <built-in function BoolProperty>, {'name': 'Set Framerate', 'default': True, 'description': "Change the scene's render settings to 60 fps", 'attr': 'set_frame_rate'}> =
<_PropertyDeferred, <built-in function BoolProperty>, {'name': 'Set Framerate', 'default': True, 'description': "Change the scene's render settings to 60 fps", 'attr': 'set_frame_rate'}>
is_loop: <_PropertyDeferred, <built-in function BoolProperty>, {'name': 'Loop', 'default': True, 'attr': 'is_loop'}> =
<_PropertyDeferred, <built-in function BoolProperty>, {'name': 'Loop', 'default': True, 'attr': 'is_loop'}>
is_anm_data_text: <_PropertyDeferred, <built-in function BoolProperty>, {'name': 'Anm Text', 'default': True, 'description': 'Output Data to a JSON file', 'attr': 'is_anm_data_text'}> =
<_PropertyDeferred, <built-in function BoolProperty>, {'name': 'Anm Text', 'default': True, 'description': 'Output Data to a JSON file', 'attr': 'is_anm_data_text'}>
remove_pre_animation: <_PropertyDeferred, <built-in function BoolProperty>, {'name': '既にあるアニメーションを削除', 'default': True, 'attr': 'remove_pre_animation'}> =
<_PropertyDeferred, <built-in function BoolProperty>, {'name': '既にあるアニメーションを削除', 'default': True, 'attr': 'remove_pre_animation'}>
set_frame: <_PropertyDeferred, <built-in function BoolProperty>, {'name': 'フレーム開始・終了位置を調整', 'default': True, 'attr': 'set_frame'}> =
<_PropertyDeferred, <built-in function BoolProperty>, {'name': 'フレーム開始・終了位置を調整', 'default': True, 'attr': 'set_frame'}>
ignore_automatic_bone: <_PropertyDeferred, <built-in function BoolProperty>, {'name': 'Twisterボーンを除外', 'default': True, 'attr': 'ignore_automatic_bone'}> =
<_PropertyDeferred, <built-in function BoolProperty>, {'name': 'Twisterボーンを除外', 'default': True, 'attr': 'ignore_automatic_bone'}>
is_location: <_PropertyDeferred, <built-in function BoolProperty>, {'name': '位置', 'default': True, 'attr': 'is_location'}> =
<_PropertyDeferred, <built-in function BoolProperty>, {'name': '位置', 'default': True, 'attr': 'is_location'}>
is_rotation: <_PropertyDeferred, <built-in function BoolProperty>, {'name': '回転', 'default': True, 'attr': 'is_rotation'}> =
<_PropertyDeferred, <built-in function BoolProperty>, {'name': '回転', 'default': True, 'attr': 'is_rotation'}>
is_scale: <_PropertyDeferred, <built-in function BoolProperty>, {'name': '拡縮', 'default': False, 'attr': 'is_scale'}> =
<_PropertyDeferred, <built-in function BoolProperty>, {'name': '拡縮', 'default': False, 'attr': 'is_scale'}>
is_tangents: <_PropertyDeferred, <built-in function BoolProperty>, {'name': 'Tangents', 'default': False, 'attr': 'is_tangents'}> =
<_PropertyDeferred, <built-in function BoolProperty>, {'name': 'Tangents', 'default': False, 'attr': 'is_tangents'}>
def
invoke(self, context, event):
50 def invoke(self, context, event): 51 prefs = common.preferences() 52 if prefs.anm_default_path: 53 self.filepath = common.default_cm3d2_dir(prefs.anm_default_path, None, "anm") 54 else: 55 self.filepath = common.default_cm3d2_dir(prefs.anm_import_path, None, "anm") 56 self.scale = prefs.scale 57 context.window_manager.fileselect_add(self) 58 return {'RUNNING_MODAL'}
def
draw(self, context):
60 def draw(self, context): 61 self.layout.prop(self, 'scale') 62 self.layout.prop(self, 'set_frame_rate' , icon=compat.icon('RENDER_ANIMATION')) 63 self.layout.prop(self, 'is_loop' , icon=compat.icon('LOOP_BACK' )) 64 self.layout.prop(self, 'is_anm_data_text', icon=compat.icon('TEXT' )) 65 66 box = self.layout.box() 67 box.prop(self, 'remove_pre_animation', icon='DISCLOSURE_TRI_DOWN') 68 box.prop(self, 'set_frame', icon='NEXT_KEYFRAME') 69 box.prop(self, 'ignore_automatic_bone', icon='X') 70 71 box = self.layout.box() 72 box.label(text="読み込むアニメーション情報") 73 column = box.column(align=True) 74 column.prop(self, 'is_location', icon=compat.icon('CON_LOCLIKE')) 75 column.prop(self, 'is_rotation', icon=compat.icon('CON_ROTLIKE')) 76 row = column.row() 77 row.prop(self, 'is_scale', icon=compat.icon('CON_SIZELIKE')) 78 row.enabled = False 79 column.prop(self, 'is_tangents', icon=compat.icon('IPO_BEZIER' ))
def
execute(self, context):
81 def execute(self, context): 82 prefs = common.preferences() 83 prefs.anm_import_path = self.filepath 84 prefs.scale = self.scale 85 86 try: 87 file = open(self.filepath, 'rb') 88 except: 89 self.report(type={'ERROR'}, message=f_tip_("ファイルを開くのに失敗しました、アクセス不可かファイルが存在しません。file={}", self.filepath)) 90 return {'CANCELLED'} 91 92 # ヘッダー 93 ext = common.read_str(file) 94 if ext != 'CM3D2_ANIM': 95 self.report(type={'ERROR'}, message="これはカスタムメイド3D2のモーションファイルではありません") 96 return {'CANCELLED'} 97 anm_version = struct.unpack('<i', file.read(4))[0] 98 first_channel_id = struct.unpack('<B', file.read(1))[0] 99 if first_channel_id != 1: 100 self.report(type={'ERROR'}, message=f_tip_("Unexpected first channel id = {id} (should be 1).", id=first_channel_id)) 101 return {'CANCELLED'} 102 103 104 anm_data = {} 105 for anim_data_index in range(9**9): 106 path = common.read_str(file) 107 108 base_bone_name = path.split('/')[-1] 109 if base_bone_name not in anm_data: 110 anm_data[base_bone_name] = {'path': path} 111 anm_data[base_bone_name]['channels'] = {} 112 113 for channel_index in range(9**9): 114 channel_id = struct.unpack('<B', file.read(1))[0] 115 channel_id_str = channel_id 116 if channel_id <= 1: 117 break 118 anm_data[base_bone_name]['channels'][channel_id_str] = [] 119 channel_data_count = struct.unpack('<i', file.read(4))[0] 120 for channel_data_index in range(channel_data_count): 121 frame = struct.unpack('<f', file.read(4))[0] 122 data = struct.unpack('<3f', file.read(4 * 3)) 123 124 anm_data[base_bone_name]['channels'][channel_id_str].append({'frame': frame, 'f0': data[0], 'f1': data[1], 'f2': data[2]}) 125 126 if channel_id == 0: 127 break 128 129 if self.is_anm_data_text: 130 if "AnmData" in context.blend_data.texts: 131 txt = context.blend_data.texts["AnmData"] 132 txt.clear() 133 else: 134 txt = context.blend_data.texts.new("AnmData") 135 import json 136 txt.write( json.dumps(anm_data, ensure_ascii=False, indent=2) ) 137 138 if self.set_frame_rate: 139 context.scene.render.fps = 60 140 fps = context.scene.render.fps 141 142 ob = context.active_object 143 arm = ob.data 144 pose = ob.pose 145 base_bone = arm.get('BaseBone') 146 if base_bone: 147 base_bone = arm.bones.get(base_bone) 148 149 anim = ob.animation_data 150 if not anim: 151 anim = ob.animation_data_create() 152 action = anim.action 153 if not action: 154 action = context.blend_data.actions.new(os.path.basename(self.filepath)) 155 anim.action = action 156 fcurves = action.fcurves 157 else: 158 action.name = os.path.basename(self.filepath) 159 fcurves = action.fcurves 160 if self.remove_pre_animation: 161 for fcurve in fcurves: 162 fcurves.remove(fcurve) 163 164 max_frame = 0 165 bpy.ops.object.mode_set(mode='OBJECT') 166 found_unknown = [] 167 found_tangents = [] 168 for bone_name, bone_data in anm_data.items(): 169 if self.ignore_automatic_bone: 170 if re.match(r"Kata_[RL]", bone_name): 171 continue 172 if re.match(r"Uppertwist1_[RL]", bone_name): 173 continue 174 if re.match(r"momoniku_[RL]", bone_name): 175 continue 176 177 if bone_name not in pose.bones: 178 bone_name = common.decode_bone_name(bone_name) 179 if bone_name not in pose.bones: 180 continue 181 bone = arm.bones[bone_name] 182 pose_bone = pose.bones[bone_name] 183 184 loc_fcurves = None 185 186 locs = {} 187 loc_tangents = {} 188 quats = {} 189 quat_tangents = {} 190 for channel_id, channel_data in bone_data['channels'].items(): 191 192 if channel_id in [100, 101, 102, 103]: 193 for data in channel_data: 194 frame = data['frame'] 195 if frame not in quats: 196 quats[frame] = [None, None, None, None] 197 198 if channel_id == 103: 199 quats[frame][0] = data['f0'] 200 elif channel_id == 100: 201 quats[frame][1] = data['f0'] 202 elif channel_id == 101: 203 quats[frame][2] = data['f0'] 204 elif channel_id == 102: 205 quats[frame][3] = data['f0'] 206 207 #tangents = (data['f1'], data['f2']) 208 #if (data['f1']**2 + data['f2']**2) ** .5 > 0.01: 209 # found_tangents.append(tangents) 210 if frame not in quat_tangents: 211 quat_tangents[frame] = {'in': [None, None, None, None], 'out': [None, None, None, None]} 212 213 if channel_id == 103: 214 quat_tangents[frame]['in' ][0] = data['f1'] 215 quat_tangents[frame]['out'][0] = data['f2'] 216 elif channel_id == 100: 217 quat_tangents[frame]['in' ][1] = data['f1'] 218 quat_tangents[frame]['out'][1] = data['f2'] 219 elif channel_id == 101: 220 quat_tangents[frame]['in' ][2] = data['f1'] 221 quat_tangents[frame]['out'][2] = data['f2'] 222 elif channel_id == 102: 223 quat_tangents[frame]['in' ][3] = data['f1'] 224 quat_tangents[frame]['out'][3] = data['f2'] 225 226 elif channel_id in [104, 105, 106]: 227 for data in channel_data: 228 frame = data['frame'] 229 if frame not in locs: 230 locs[frame] = [None, None, None] 231 232 if channel_id == 104: 233 locs[frame][0] = data['f0'] 234 elif channel_id == 105: 235 locs[frame][1] = data['f0'] 236 elif channel_id == 106: 237 locs[frame][2] = data['f0'] 238 239 #tangents = (data['f1'], data['f2']) 240 #if (data['f1']**2 + data['f2']**2) ** .5 > 0.05: 241 # found_tangents.append(tangents) 242 if frame not in loc_tangents: 243 loc_tangents[frame] = {'in': [None, None, None], 'out': [None, None, None]} 244 245 if channel_id == 104: 246 loc_tangents[frame]['in' ][0] = data['f1'] 247 loc_tangents[frame]['out'][0] = data['f2'] 248 elif channel_id == 105: 249 loc_tangents[frame]['in' ][1] = data['f1'] 250 loc_tangents[frame]['out'][1] = data['f2'] 251 elif channel_id == 106: 252 loc_tangents[frame]['in' ][2] = data['f1'] 253 loc_tangents[frame]['out'][2] = data['f2'] 254 255 elif channel_id not in found_unknown: 256 found_unknown.append(channel_id) 257 self.report(type={'INFO'}, message=f_tip_("Unknown channel id {num}", num=channel_id)) 258 259 ''' 260 for frame, (loc, quat) in enumerate(zip(locs.values(), quats.values())): 261 loc = mathutils.Vector(loc) * self.scale 262 quat = mathutils.Quaternion(quat) 263 264 loc_mat = mathutils.Matrix.Translation(loc).to_4x4() 265 rot_mat = quat.to_matrix().to_4x4() 266 mat = compat.mul(loc_mat, rot_mat) 267 268 bone_loc = bone.head_local.copy() 269 bone_quat = bone.matrix.to_quaternion() 270 271 if bone.parent: 272 parent = bone.parent 273 else: 274 parent = base_bone 275 276 if parent: 277 mat = compat.convert_cm_to_bl_bone_space(mat) 278 mat = compat.mul(parent.matrix_local, mat) 279 mat = compat.convert_cm_to_bl_bone_rotation(mat) 280 pose_mat = bone.convert_local_to_pose( 281 matrix = mat, 282 matrix_local = bone.matrix_local, 283 parent_matrix = mathutils.Matrix.Identity(4), 284 parent_matrix_local = parent.matrix_local 285 ) 286 else: 287 mat = compat.convert_cm_to_bl_bone_rotation(mat) 288 mat = compat.convert_cm_to_bl_space(mat) 289 pose_mat = bone.convert_local_to_pose( 290 matrix = mat, 291 matrix_local = bone.matrix_local 292 ) 293 294 if self.is_location: 295 pose_bone.location = pose_mat.to_translation() 296 pose_bone.keyframe_insert('location' , frame=frame * fps, group=pose_bone.name) 297 if self.is_rotation: 298 pose_bone.rotation_quaternion = pose_mat.to_quaternion() 299 pose_bone.keyframe_insert('rotation_quaternion', frame=frame * fps, group=pose_bone.name) 300 if max_frame < frame * fps: 301 max_frame = frame * fps 302 ''' 303 304 def _apply_tangents(fcurves, keyframes, tangents): 305 for axis_index, axis_keyframes in enumerate(keyframes): 306 fcurve = fcurves[axis_index] 307 fcurve.update() # make sure automatic handles are calculated 308 axis_keyframes.sort() # make sure list is in order 309 for keyframe_index, frame in enumerate(axis_keyframes): 310 tangent_in = tangents[frame]['in' ][axis_index] 311 tangent_out = tangents[frame]['out'][axis_index] 312 313 vec_in = mathutils.Vector((1, tangent_in / fps)) 314 vec_out = mathutils.Vector((1, tangent_out / fps)) 315 316 this_keyframe = fcurve.keyframe_points[keyframe_index ] 317 next_keyframe = fcurve.keyframe_points[keyframe_index+1] if keyframe_index+1 < len(axis_keyframes) else None 318 last_keyframe = fcurve.keyframe_points[keyframe_index-1] if keyframe_index-1 >= 0 else None 319 320 if vec_in.y != vec_out.y: 321 this_keyframe.handle_left_type = 'FREE' 322 this_keyframe.handle_right_type = 'FREE' 323 else: 324 this_keyframe.handle_left_type = 'ALIGNED' 325 this_keyframe.handle_right_type = 'ALIGNED' 326 327 this_co = mathutils.Vector(this_keyframe.co) 328 next_co = mathutils.Vector(next_keyframe.co) if next_keyframe else None 329 last_co = mathutils.Vector(last_keyframe.co) if last_keyframe else None 330 if not next_keyframe: 331 next_keyframe = fcurve.keyframe_points[0] 332 if next_keyframe and next_keyframe != this_keyframe: 333 next_co = mathutils.Vector(next_keyframe.co) 334 next_co.x += max_frame 335 if not last_keyframe: 336 last_keyframe = fcurve.keyframe_points[len(axis_keyframes)-1] 337 if last_keyframe and last_keyframe != this_keyframe: 338 last_co = mathutils.Vector(last_keyframe.co) 339 last_co.x -= max_frame 340 341 factor = 3 342 dist_in = (last_co.x - this_co.x) / factor if factor and last_co else None 343 dist_out = (next_co.x - this_co.x) / factor if factor and next_co else None 344 if not dist_in and not dist_out: 345 dist_in = this_keyframe.handle_left[0] - this_co.x 346 dist_out = this_keyframe.handle_right[0] - this_co.x 347 elif not dist_in: 348 dist_in = -dist_out 349 elif not dist_out: 350 dist_out = -dist_in 351 352 this_keyframe.handle_left = vec_in * dist_in + this_co 353 this_keyframe.handle_right = vec_out * dist_out + this_co 354 355 356 if self.is_location: 357 loc_fcurves = [None, None, None] 358 loc_keyframes = [[],[],[]] 359 rna_data_path = 'pose.bones["{bone_name}"].location'.format(bone_name=bone.name) 360 for axis_index in range(0, 3): 361 new_fcurve = fcurves.find(rna_data_path, index=axis_index) 362 if not new_fcurve: 363 new_fcurve = fcurves.new(rna_data_path, index=axis_index, action_group=pose_bone.name) 364 loc_fcurves[axis_index] = new_fcurve 365 366 def _convert_loc(loc) -> mathutils.Vector: 367 loc = mathutils.Vector(loc) * self.scale 368 #bone_loc = bone.head_local.copy() 369 # 370 #if bone.parent: 371 # #loc.x, loc.y, loc.z = -loc.y, -loc.x, loc.z 372 # 373 # #co.x, co.y, co.z = -co.y, co.z, co.x 374 # #loc.x, loc.y, loc.z = loc.z, -loc.x, loc.y 375 # #mat = mathutils.Matrix( 376 # # [( 0, 0, 1, 0), 377 # # (-1, 0, 0, 0), 378 # # ( 0, 1, 0, 0), 379 # # ( 0, 0, 0, 1)] 380 # #) 381 # #loc = compat.mul(mat, loc) 382 # 383 # loc = compat.convert_cm_to_bl_bone_space(loc) 384 # 385 # bone_loc = bone_loc - bone.parent.head_local 386 # bone_loc.rotate(bone.parent.matrix_local.to_quaternion().inverted()) 387 #else: 388 # #loc.x, loc.y, loc.z = loc.x, loc.z, loc.y 389 # loc = compat.convert_cm_to_bl_space(loc) 390 # 391 #result_loc = loc - bone_loc 392 if bone.parent: 393 loc = compat.convert_cm_to_bl_bone_space(loc) 394 loc = compat.mul(bone.parent.matrix_local, loc) 395 else: 396 loc = compat.convert_cm_to_bl_space(loc) 397 return compat.mul(bone.matrix_local.inverted(), loc) 398 399 for frame, loc in locs.items(): 400 result_loc = _convert_loc(loc) 401 #pose_bone.location = result_loc 402 403 #pose_bone.keyframe_insert('location', frame=frame * fps, group=pose_bone.name) 404 if max_frame < frame * fps: 405 max_frame = frame * fps 406 407 for fcurve in loc_fcurves: 408 keyframe_type = 'KEYFRAME' 409 tangents = loc_tangents[frame] 410 if tangents: 411 tangents = mathutils.Vector((tangents['in'][fcurve.array_index], tangents['out'][fcurve.array_index])) 412 if tangents.magnitude < 1e-6: 413 keyframe_type = 'JITTER' 414 elif tangents.magnitude > 0.1: 415 keyframe_type = 'EXTREME' 416 417 keyframe = fcurve.keyframe_points.insert( 418 frame = frame * fps , 419 value = result_loc[fcurve.array_index], 420 options = {'FAST'} , 421 keyframe_type = keyframe_type 422 ) 423 keyframe.type = keyframe_type 424 loc_keyframes[fcurve.array_index].append(frame) 425 426 if self.is_loop: 427 for fcurve in loc_fcurves: 428 new_modifier = fcurve.modifiers.new('CYCLES') 429 430 if self.is_tangents: 431 for frame, tangents in loc_tangents.items(): 432 tangent_in = mathutils.Vector(tangents['in' ]) * self.scale 433 tangent_out = mathutils.Vector(tangents['out']) * self.scale 434 if bone.parent: 435 tangent_in = compat.convert_cm_to_bl_bone_space(tangent_in ) 436 tangent_out = compat.convert_cm_to_bl_bone_space(tangent_out) 437 else: 438 tangent_in = compat.convert_cm_to_bl_space(tangent_in ) 439 tangent_out = compat.convert_cm_to_bl_space(tangent_out) 440 tangents['in' ][:] = tangent_in [:] 441 tangents['out'][:] = tangent_out[:] 442 443 _apply_tangents(loc_fcurves, loc_keyframes, loc_tangents) 444 445 446 447 if self.is_rotation: 448 quat_fcurves = [None, None, None, None] 449 quat_keyframes = [[],[],[],[]] 450 rna_data_path = 'pose.bones["{bone_name}"].rotation_quaternion'.format(bone_name=pose_bone.name) 451 for axis_index in range(0, 4): 452 new_fcurve = fcurves.find(rna_data_path, index=axis_index) 453 if not new_fcurve: 454 new_fcurve = fcurves.new(rna_data_path, index=axis_index, action_group=pose_bone.name) 455 quat_fcurves[axis_index] = new_fcurve 456 457 458 bone_quat = bone.matrix.to_quaternion() 459 def _convert_quat(quat) -> mathutils.Quaternion: 460 quat = mathutils.Quaternion(quat) 461 #orig_quat = quat.copy() 462 '''Can't use matrix transforms here as they would mess up interpolation.''' 463 if bone.parent: 464 quat.w, quat.x, quat.y, quat.z = quat.w, -quat.z, quat.x, -quat.y 465 #quat_mat = compat.convert_cm_to_bl_bone_space(quat.to_matrix().to_4x4()) 466 #quat_mat = compat.convert_cm_to_bl_bone_rotation(quat_mat) 467 else: 468 quat.w, quat.x, quat.y, quat.z = quat.w, -quat.z, quat.x, -quat.y 469 quat = compat.mul(mathutils.Matrix.Rotation(math.radians(-90.0), 4, 'Z').to_quaternion(), quat) 470 #quat_mat = compat.convert_cm_to_bl_space(quat.to_matrix().to_4x4()) 471 #quat = compat.convert_cm_to_bl_bone_rotation(quat_mat).to_quaternion() 472 quat = compat.mul(bone_quat.inverted(), quat) 473 #quat.make_compatible(orig_quat) 474 return quat 475 476 for frame, quat in quats.items(): 477 result_quat = _convert_quat(quat) 478 #pose_bone.rotation_quaternion = result_quat.copy() 479 480 #pose_bone.keyframe_insert('rotation_quaternion', frame=frame * fps, group=pose_bone.name) 481 if max_frame < frame * fps: 482 max_frame = frame * fps 483 484 for fcurve in quat_fcurves: 485 keyframe_type = 'KEYFRAME' 486 tangents = quat_tangents[frame] 487 if tangents: 488 tangents = mathutils.Vector((tangents['in'][fcurve.array_index], tangents['out'][fcurve.array_index])) 489 if tangents.magnitude < 1e-6: 490 keyframe_type = 'JITTER' 491 elif tangents.magnitude > 0.1: 492 keyframe_type = 'EXTREME' 493 494 keyframe = fcurve.keyframe_points.insert( 495 frame = frame * fps , 496 value = result_quat[fcurve.array_index] , 497 options = {'FAST'} , 498 keyframe_type = keyframe_type 499 ) 500 keyframe.type = keyframe_type 501 quat_keyframes[fcurve.array_index].append(frame) 502 503 if self.is_loop: 504 for fcurve in quat_fcurves: 505 new_modifier = fcurve.modifiers.new('CYCLES') 506 507 if self.is_tangents: 508 for frame, tangents in quat_tangents.items(): 509 tangents['in' ][:] = _convert_quat(tangents['in' ])[:] 510 tangents['out'][:] = _convert_quat(tangents['out'])[:] 511 512 _apply_tangents(quat_fcurves, quat_keyframes, quat_tangents) 513 514 515 516 if found_tangents: 517 self.report(type={'INFO'}, message="Found the following tangent values:") 518 for f1, f2 in found_tangents: 519 self.report(type={'INFO'}, message=f_tip_("f1 = {float1}, f2 = {float2}", float1=f1, float2=f2)) 520 self.report(type={'INFO'}, message="Found the above tangent values.") 521 self.report(type={'WARNING'}, message=f_tip_("Found {count} large tangents. Blender animation may not interpolate properly. See log for more info.", count=len(found_tangents))) 522 if found_unknown: 523 self.report(type={'INFO'}, message="Found the following unknown channel IDs:") 524 for channel_id in found_unknown: 525 self.report(type={'INFO'}, message=f_tip_("id = {id}", id=channel_id)) 526 self.report(type={'INFO'}, message="Found the above unknown channel IDs.") 527 self.report(type={'WARNING'}, message=f_tip_("Found {count} unknown channel IDs. Blender animation may be missing some keyframes. See log for more info.", count=len(found_unknown))) 528 529 if self.set_frame: 530 context.scene.frame_start = 0 531 context.scene.frame_end = max_frame 532 context.scene.frame_set(0) 533 534 return {'FINISHED'}
Inherited Members
- bpy_types.Operator
- as_keywords
- poll_message_set
- builtins.bpy_struct
- keys
- values
- items
- get
- pop
- as_pointer
- keyframe_insert
- keyframe_delete
- driver_add
- driver_remove
- is_property_set
- property_unset
- is_property_readonly
- is_property_overridable_library
- property_overridable_library_set
- path_resolve
- path_from_id
- type_recast
- bl_rna_get_subclass_py
- bl_rna_get_subclass
- id_properties_ensure
- id_properties_clear
- id_properties_ui
- id_data